package org.opens.javaskill.jvm.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Date 2020/10/28 20:39
 * @Created by 99126
 */
public class CustomizeClassLoader extends ClassLoader {

    private static final String DEFAULT_DIR = "E:\\classloader";

    private String dir = DEFAULT_DIR;

    private String classLoaderName;

    public CustomizeClassLoader() {
        super();
    }

    public CustomizeClassLoader(String classLoaderName) {
        super();
        this.classLoaderName = classLoaderName;
    }

    public CustomizeClassLoader(String classLoaderName, ClassLoader parent) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    public String getDir() {
        return dir;
    }

    public void setDir(String dir) {
        this.dir = dir;
    }

    public String getClassLoaderName() {
        return classLoaderName;
    }

    public void setClassLoaderName(String classLoaderName) {
        this.classLoaderName = classLoaderName;
    }

    /**
     * 说明:
     * <pre>
     *     1. 任何类加载器都必须重写这个方法, 因为ClassLoader类的这个方法不重写会直接抛出异常.
     * </pre>
     *
     * @param name 加载的class的路径信息
     * @return 加载类的Class对象, 可以使用这个对象创建对象, 调用函数等;
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classpath = name.replace(".", "/");
        File classfile = new File(dir, classpath + ".class");
        if (!classfile.exists()) {
            throw new ClassNotFoundException("The classname " + name + " not found");
        }

        byte[] classBytes = loadClassBytes(classfile);
        if (null == classBytes || classBytes.length == 0) {
            throw new ClassNotFoundException("load the class " + name + " failed");
        }

        return this.defineClass(name, classBytes, 0, classBytes.length);
    }

    /**
     * 功能:
     * <p>将文件转换为byte[]</p>
     *
     * @param classfile class文件对象
     */
    private byte[] loadClassBytes(File classfile) {
        try (
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                FileInputStream fis = new FileInputStream(classfile);
        ) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            bos.flush();
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

class MyClassLoadTest {

    public static void main(String[] args) {
        CustomizeClassLoader classLoader = new CustomizeClassLoader("my-classloader");
        try {
            Class<?> aClass = classLoader.loadClass("org.opens.javaskill.jvm.classloader.MyLoadObject");
            System.out.println(aClass.getClassLoader());

            Object instance = aClass.newInstance();
            Method method = aClass.getMethod("hello", new Class[]{});
            Object result = method.invoke(instance, new Object[]{});
            System.out.println(result);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}

class MyClassLoadTest2 {

    public static void main(String[] args) {
        CustomizeClassLoader classLoader1 = new CustomizeClassLoader("my-classloader1");
        CustomizeClassLoader classLoader2 = new CustomizeClassLoader("my-classloader2", classLoader1);
        classLoader2.setDir("H://");
        try {
            Class<?> aClass = classLoader2.loadClass("org.opens.javaskill.jvm.classloader.MyLoadObject");
            System.out.println(((CustomizeClassLoader) aClass.getClassLoader()).getClassLoaderName());

            Object instance = aClass.newInstance();
            Method method = aClass.getMethod("hello", new Class[]{});
            Object result = method.invoke(instance, new Object[]{});
            System.out.println(result);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}

class MyClassLoadTest3 {

    /**
     * 输出:
     * <pre>
     *  607635164
     *  529116035
     * </pre>
     * 说明:
     * <pre>
     *  1. 同一个类, 类加载器不同则对应的Class对象不同.
     * </pre>
     */
    public static void main(String[] args) {
        CustomizeClassLoader classLoader1 = new CustomizeClassLoader("my-classloader1");
        CustomizeClassLoader classLoader2 = new CustomizeClassLoader("my-classloader2");
        try {
            Class<?> aClass1 = classLoader1.loadClass("org.opens.javaskill.jvm.classloader.MyLoadObject");
            Class<?> aClass2 = classLoader2.loadClass("org.opens.javaskill.jvm.classloader.MyLoadObject");

            System.out.println(aClass1.hashCode());
            System.out.println(aClass2.hashCode());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

class MyClassLoadTest4 {

    public static void main(String[] args) {
        CustomizeClassLoader classLoader = new CustomizeClassLoader("my-classloader");
        classLoader.setDir("E:\\classloader\\classloader2");
        try {
            Class<?> aClass = classLoader.loadClass("org.opens.javaskill.jvm.classloader.MyLoadObject");
            System.out.println(aClass.getClassLoader());

            Object instance = aClass.newInstance();
            Method method = aClass.getMethod("hello", new Class[]{});
            Object result = method.invoke(instance, new Object[]{});
            System.out.println(result);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}